define(['jquery', 'template'], function ($, template) {


    function calendar(options) {

        var me = this;
        var defaults = this.defaults = {
            selector: '',
            viewType: 'D',
            singleView: false,
            format: 'yyyy-mm-dd',
            weekStart: 0,
            value: '',
            offset: [0, 0],
            yearRange: [-2, 0], // 默认[-2,0]表示[2016+(-2),2016+0],[2014,2016]
            enableFutureDate: false, // 是否显示未来的日期
            enableToday: false
        };

        $.extend(defaults, options, {
            tpl: {
                calendar: '<div class="calendar" style="top:{{position.top}};left:{{position.left}}">\
                    {{if !singleView}}\
                    <div class="calendar-type">\
                        <div class="calendar-type-day" data-viewtype="D">天</div>\
                        <div class="calendar-type-week"  data-viewtype="W">周</div>\
                        <div class="calendar-type-month"  data-viewtype="M">月</div>\
                        <div class="calendar-type-season"  data-viewtype="Q">季</div>\
                        <span class="calendar-close">&times;</span>\
                    </div>\
                    {{/if}}\
                    <div class="calendar-body"></div>\
                    <div class="calendar-foot"><input type="text" class="calendar-start-date" readonly><span class="calendar-range-symbol">至</span><input type="text" class="calendar-end-date" readonly><input type="button" class="calendar-btn-ok" value="确定"><input type="button" class="calendar-btn-cancel" value="取消"></div>\
                </div>',
                combox: '{{each comboxes as c i}}\
                            <div class="calendar-combox calendar-combox-{{c.field}}" data-field="{{c.field}}" style="width:{{100/comboxes.length}}%">\
                                <div class="calendar-combox-selected">{{c.selected}}{{c.suffix}}</div>\
                                <ul class="calendar-combox-options">\
                                    {{each c.options as v i}}\
                                    <li data-value="{{v.value}}" class="{{v.cls}}">{{v.label}}</li>\
                                    {{/each}}\
                                </ul>\
                            </div>\
                        {{/each}}',
                weekHead: '<tr class="calendar-week-head">{{each weekHead as v i}}<td>{{v}}</td>{{/each}}</tr>',
                D: '<table class="{{cls}}">\
                        <thead>\
                            <tr>\
                                <td class="calendar-combox-wrap" colspan="7">\
                                {{#combox}}\
                                </td>\
                            </tr>\
                            {{#weekhead}}\
                        </thead>\
                        <tbody>\
                            <tr>\
                            {{each dates as v i}}\
                                <td class="{{v.cls}}" data-type="{{type}}" data-startdate="{{v.date}}" data-enddate="{{v.date}}">{{v.day}}</td>\
                                {{if (i+1)%7==0 && i!=dates.length-1}}\
                                    </tr>\
                                    <tr>\
                                {{/if}}\
                            {{/each}}\
                            </tr>\
                        </tbody>\
                    </table>',
                W: '<table class="{{cls}}">\
                        <thead>\
                            <tr>\
                                <td colspan="7">\
                                {{#combox}}\
                                </td>\
                            </tr>\
                        {{#weekhead}}\
                        </thead>\
                        <tbody>\
                            {{each dates as v i}}\
                            <tr>\
                                 <td colspan="7" class="{{v.cls}}" data-type="{{type}}"  data-startdate="{{v.startDate}}" data-enddate="{{v.endDate}}">\
                                    {{each v.week as v1 i1}}\
                                    <span class="week-day {{v1.cls}}">{{v1.day}}</span>\
                                    {{/each}}\
                                </td>\
                            </tr>\
                            {{/each}}\
                        </tbody>\
                    </table>',
                M: '<table  class="{{cls}}">\
                        <thead>\
                            <tr>\
                                <td colspan="7">\
                                {{#combox}}\
                                </td>\
                            </tr>\
                        </thead>\
                        <tbody>\
                            <tr>\
                            {{each dates as v i}}\
                                <td class="{{v.cls}}" data-type="{{type}}" data-startdate="{{v.startDate}}" data-enddate="{{v.endDate}}">{{i+1}}</td>\
                                {{if (i+1)%3==0 && i!=dates.length-1}}\
                                </tr>\
                                <tr>\
                                {{/if}}\
                            {{/each}}\
                            </tr>\
                        </tbody>\
                    </table>',
                Q: '<table  class="{{cls}}">\
                    <thead>\
                    <tr>\
                    <td colspan="7">\
                    {{#combox}}\
                    </td>\
                    </tr>\
                    </thead>\
                        <tbody>\
                            <tr>\
                            {{each dates as v i}}\
                                <td class="{{v.cls}}" data-type="{{type}}" data-startdate="{{v.startDate}}" data-enddate="{{v.endDate}}">{{i+1}}</td>\
                                {{if (i+1)%2==0 && i!=dates.length-1}}\
                                </tr>\
                                <tr>\
                                {{/if}}\
                            {{/each}}\
                            </tr>\
                        </tbody>\
                    </table>'

            }

        });

        var $elem = this.$elem = $(defaults.selector);
        var elemPos = $elem.offset();

        defaults.position = {
            left: elemPos.left + defaults.offset[0] + 'px',
            top: elemPos.top + $elem.outerHeight() + defaults.offset[1] + 'px'
        };


        var $calendar = this.$calendar = $(template.compile(defaults.tpl.calendar)(defaults)).appendTo('body');


        $elem.on('focus', function () {
            me.show();
        });

        this.NAMESPACE = 'calendar';

        var EVENT_CLICK = 'click.' + this.NAMESPACE;

        $calendar.on(EVENT_CLICK, '[data-viewtype]', function () {
            $(this).addClass('active').siblings('.active').removeClass('active');
            me.value = $.extend({}, me.rawValue);
            me.showViewType = $(this).data('viewtype');
            me.updateDom();
        }).on(EVENT_CLICK, '.calendar-combox', function () {

            $(this).toggleClass('calendar-combox-expanded').siblings().removeClass('calendar-combox-expanded');

        }).on(EVENT_CLICK, '.calendar-combox-options li', function (e) {
            e.stopPropagation();
            var data = $(this).closest('.calendar-combox').data();
            me.value[data.field] = $(this).data('value');
            me.updateDom();

        }).on(EVENT_CLICK, '[data-type]', function () {
            var $this = $(this);
            if ($this.hasClass('disabled')) {
                return;
            }
            $this.closest('table').find('.selected').removeClass('selected');
            $this.addClass('selected');
            me.selectDate();
        }).on(EVENT_CLICK, '.calendar-btn-ok', function () {
            $calendar.trigger('btn.ok.click', me);
            me.rawValue = me.splitDate(me.getValue().startdate);
            me.hide();

        }).on(EVENT_CLICK, '.calendar-close,.calendar-btn-cancel', function () {
            $calendar.trigger('btn.cancel.click', me);
            me.hide();
        });

        if (defaults.value || $elem.val()) {
            this.value = this.splitDate(defaults.value || $elem.val());
        } else {
            var now = new Date();
            this.value = {
                year: now.getFullYear(),
                month: now.getMonth() + 1,
                day: now.getDate()
            };
            this.setValue();
        }
        this.rawValue = $.extend({}, this.value);
        this.showViewType = defaults.viewType;

        $(document).on('click', function (e) {
            var c = $(e.target).closest('.calendar');
            var s = $(e.target).closest(defaults.selector);
            if (c.length == 0 && s.length == 0) {
                me.hide();
            }
        });
    }

    calendar.prototype = {
        constructor: calendar,

        show: function () {
            this.updateDom();
            this.$calendar.find('[data-viewtype=' + this.showViewType + ']').addClass('active').siblings('.active').removeClass('active');
            this.$calendar.show();
        },
        hide: function () {
            this.$calendar.hide();
        },

        setValue: function () {
            if (this.$elem.is('input')) {
                this.$elem.val(this.formatDate(this.value.year, this.value.month, this.value.day));
            }
        },

        getValue: function () {
            return this.$calendar.find('.selected').data();
        },

        bindEvent: function (event, callback) {
            if ($.isPlainObject(event)) {
                this.$calendar.bind(event);
            } else {
                this.$calendar.bind(event, callback);
            }
        },

        selectDate: function () {
            var $startDate = this.$calendar.find('.calendar-start-date');
            var $endDate = this.$calendar.find('.calendar-end-date');
            var $selected = this.$calendar.find('.selected');
            $startDate.val($selected.data('startdate'));
            $endDate.val($selected.data('enddate'));
        },

        updateDom: function () {

            var tplData = $.extend({
                type: this.showViewType,
                cls: this.NAMESPACE + '-' + this.showViewType,
                dates: this.getViewData(),
                combox: this._initCombox(),
                weekhead:this._initWeekHead()
            }, this.value);
            this.$calendar.find('.calendar-body').html(template.compile(this.defaults.tpl[this.showViewType])(tplData));
            this.selectDate();
        },


        _getYearRange:function(){
            var year = new Date().getFullYear(),
                yearMax = year + this.defaults.yearRange[1],
                yearMin = year + this.defaults.yearRange[0],
                yearConfig = [];


            for (var i = yearMin; i <= yearMax; i++) {
                yearConfig.push({
                    label: i,
                    value: i,
                    cls: this.value.year == i ? 'combox-option-selected' : ''
                })
            }
            return yearConfig;
        },

        _getMonthRange:function(){
            var now = new Date(),
                nowYear = now.getFullYear(),
                nowMonth = now.getMonth() + 1,
                monthConfig = [];

            if (this.value.year == nowYear && !this.defaults.enableFutureDate) {
                for (var i = 1; i <= nowMonth; i++) {
                    monthConfig.push({
                        label: i,
                        value: i,
                        cls: this.value.month == i ? 'combox-option-selected' : ''
                    })
                }
            } else {
                for (var i = 1; i <= 12; i++) {
                    monthConfig.push({
                        label: i,
                        value: i,
                        cls: this.value.month == i ? 'combox-option-selected' : ''
                    })
                }
            }
            return monthConfig;
        },

        _initCombox:function(){
            switch (this.showViewType) {
                case 'W':
                case 'D':
                    return template.compile(this.defaults.tpl.combox)({
                        comboxes: [{
                            selected:  this.value.year,
                            suffix: '年',
                            field: 'year',
                            options: this._getYearRange()
                        }, {
                            selected: this.value.month,
                            suffix: '月',
                            field: 'month',
                            options: this._getMonthRange()
                        }]
                    });
                case 'M':
                case 'Q':
                    return template.compile(this.defaults.tpl.combox)({
                        comboxes: [{
                            selected: this.value.year,
                            suffix: '年',
                            field: 'year',
                            options: this._getYearRange()
                        }]
                    });
            }
            return '';
        },

        _initWeekHead: function () {
            return template.compile(this.defaults.tpl.weekHead)({
                weekHead: ['日', '一', '二', '三', '四', '五', '六']
            });
        },

        formatDate: function (year, month, day) {
            return this.defaults.format.replace(/[ymd]+/gi, function (m) {
                m = m.toLowerCase();
                if (m == 'yyyy') {
                    return year;
                } else if (m == 'mm') {
                    return month < 10 ? '0' + month : month;
                } else {
                    return day < 10 ? '0' + day : day;
                }
            });
        },

        splitDate: function (v) {
            var date = {};
            var yearStartIndex = 0;
            var monthStartIndex = 0;
            var dayStartIndex = 0;
            var formatRegStr = this.defaults.format.replace(/[ymd]+|[^ymd]/gi, function (m, i) {
                m = m.toLowerCase();
                if (m == 'yyyy') {
                    yearStartIndex = i;
                    return '\\d{4}';
                } else if (m == 'mm') {
                    monthStartIndex = i;
                    return '\\d{2}';
                } else if (m == 'dd') {
                    dayStartIndex = i;
                    return '\\d{2}';
                } else {
                    return '\\' + m;
                }
            });
            var formatReg = new RegExp(formatRegStr);
            if (formatReg.test(v)) {
                date = {
                    year: v.substr(yearStartIndex, 4) - 0,
                    month: v.substr(monthStartIndex, 2) - 0,
                    day: v.substr(dayStartIndex, 2) - 0
                };
            } else {
                throw new TypeError(v + '与format格式(' + this.defaults.format + ')不一致');
            }
            return date;
        },

        getViewData: function () {
            var now = new Date(),
                nowPart = {
                    year: now.getFullYear(),
                    month: now.getMonth() + 1,
                    day: now.getDate()
                };
            switch (this.showViewType) {
                case 'D':
                    var dates = this.getDatesOfMonth(this.value.year, this.value.month);
                    for (var i = 0, j = dates.length; i < j; i++) {
                        var compareResult = this.compareDate(dates[i], nowPart);
                        if (!this.defaults.enableFutureDate && (compareResult == 1 || (!this.defaults.enableToday && compareResult == 0 ))) {
                            dates[i].cls += ' disabled';
                        } else if (this.isRawValueIn(dates[i].date)) {
                            dates[i].cls += ' selected';
                        }
                    }
                    return dates;
                case 'W':
                    var dates = this.getDatesOfMonth(this.value.year, this.value.month);
                    var weekData = {};
                    for (var i = 0, j = dates.length / 7; i < j; i++) {
                        weekData[i] = {
                            startDate: dates[i * 7].date,
                            endDate: dates[(i + 1) * 7 - 1].date,
                            week: [],
                            cls: 'week-cell'
                        };
                        if (!this.defaults.enableFutureDate && this.compareDate(dates[i * 7], nowPart) > 0) {
                            weekData[i].cls += ' disabled';
                        } else if (this.isRawValueIn(weekData[i])) {
                            weekData[i].cls += ' selected';
                        }
                        for (var k = 0; k < 7; k++) {
                            weekData[i].week.push(dates[i * 7 + k]);
                        }
                    }
                    return weekData;
                case 'M':
                    var monthData = [];
                    for (var i = 0; i < 12; i++) {
                        var monthDataItem = {
                            startDate: this.formatDate(this.value.year, i + 1, 1),
                            endDate: this.getMonthMaxDay(this.value.year, i + 1),
                            cls: 'month-cell'
                        };
                        if (!this.defaults.enableFutureDate && this.compareDate({year: this.value.year, month: i + 1, day: 1}, nowPart) > 0) {
                            monthDataItem.cls += ' disabled';
                        } else if (this.isRawValueIn(monthDataItem)) {
                            monthDataItem.cls += ' selected';
                        }
                        monthData.push(monthDataItem);
                    }
                    return monthData;
                case 'Q':
                    var seasonData = [];
                    for (var i = 0; i < 4; i++) {
                        var seasonDataItem = {
                            startDate: this.formatDate(this.value.year, i * 3 + 1, 1),
                            endDate: this.getMonthMaxDay(this.value.year, (i + 1) * 3),
                            cls: 'season-cell'
                        }
                        if (!this.defaults.enableFutureDate && this.compareDate({year: this.value.year, month: i * 3 + 1, day: 1}, nowPart) > 0) {
                            seasonDataItem.cls += ' disabled';
                        } else if (this.isRawValueIn(seasonDataItem)) {
                            seasonDataItem.cls += ' selected';
                        }
                        seasonData.push(seasonDataItem);
                    }
                    return seasonData;
            }

        },

        compareDate: function (a, b) {
            a = new Date(a.year, a.month - 1, a.day).getTime();
            b = new Date(b.year, b.month - 1, b.day).getTime();
            if (a > b) {
                return 1;
            } else if (a == b) {
                return 0;
            } else {
                return -1;
            }
        },

        isRawValueIn: function (range) {
            var rawValueDate = new Date(this.rawValue.year, this.rawValue.month - 1, this.rawValue.day);
            if (typeof(range) == 'string') {
                var startDateSplit = endDateSplit = this.splitDate(range);
            } else {
                var startDateSplit = this.splitDate(range.startDate);
                var endDateSplit = this.splitDate(range.endDate);
            }
            var startDate = new Date(startDateSplit.year, startDateSplit.month - 1, startDateSplit.day);
            var endDate = new Date(endDateSplit.year, endDateSplit.month - 1, endDateSplit.day);
            if (rawValueDate >= startDate && rawValueDate <= endDate) {
                return true;
            }
            return false;
        },

        getMonthMaxDay: function (year, month) {
            return this.formatDate(year, month, new Date(year, month, 0).getDate());
        },

        getDatesOfMonth: function (year, month) {
            var dates = [];
            // 初始化为month第一天的日期
            var date = new Date(year, month - 1, 1);
            var dayOfWeek = date.getDay();

            var lastMonthDays = 0;
            if (dayOfWeek != this.defaults.weekStart) {
                date.setDate(0);  // 上月最后一天
                var prevDate = date.getDate();
                var lastMonthDays = dayOfWeek - this.defaults.weekStart;
                if (lastMonthDays < 0) {
                    lastMonthDays += 7;
                }
                // -1是因为i可以等于0
                for (var i = lastMonthDays - 1; i >= 0; i--) {
                    dates.push({
                        'date': this.formatDate(date.getFullYear(), date.getMonth() + 1, prevDate - i),
                        'year': date.getFullYear(),
                        'month': date.getMonth() + 1,
                        'day': prevDate - i,
                        'cls': 'prev-month'
                    });
                }
            }
            date.setFullYear(year, month, 0);

            for (var i = 1, j = date.getDate(); i <= j; i++) {
                dates.push({
                    'date': this.formatDate(date.getFullYear(), date.getMonth() + 1, i),
                    'year': date.getFullYear(),
                    'month': date.getMonth() + 1,
                    'day': i,
                    'cls': 'curr-month'
                });
            }


            // 上月日期总数+当月日期总数之和不为7的倍数才会显示下月的部分日期
            if (dates.length % 7 > 0) {
                date.setFullYear(year, month, 1);
                for (var i = 1, j = 7 - dates.length % 7; i <= j; i++) {
                    dates.push({
                        'date': this.formatDate(date.getFullYear(), date.getMonth() + 1, i),
                        'year': date.getFullYear(),
                        'month': date.getMonth() + 1,
                        'day': i,
                        'cls': 'next-month'
                    });
                }
            }
            return dates;
        }


    };


    return function factory(options) {
        return new calendar(options);
    };


});